<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<HTML>
<HEAD>
    <TITLE>Tutorial</TITLE>
    <LINK REL="stylesheet" HREF="../../../../boost.css">
    <LINK REL="stylesheet" HREF="../theme/iostreams.css">
</HEAD>
<BODY>

<!-- Begin Banner -->

    <H1 CLASS="title">Tutorial</H1>
    <HR CLASS="banner">

<!-- End Banner -->

<!-- Begin Nav -->

<DIV CLASS='nav'>
    <A HREF='container_sink.html'><IMG BORDER=0 WIDTH=19 HEIGHT=19 SRC='../../../../doc/src/images/prev.png'></A>
    <A HREF='tutorial.html'><IMG BORDER=0 WIDTH=19 HEIGHT=19 SRC='../../../../doc/src/images/up.png'></A>
    <A HREF='writing_filters.html'><IMG BORDER=0 WIDTH=19 HEIGHT=19 SRC='../../../../doc/src/images/next.png'></A>
</DIV>

<!-- End Nav -->

<A NAME="container_device"></A>
<H2>2.1.4. Writing a <CODE>container_device</CODE></H2>

<P>Suppose you want to write a Device for reading from and writing to an STL container. In order for combined reading and writing to be useful, you will also need to support seeking within the container. There are several types of Devices which combine reading and writing; they differ according to whether there are two separate character sequences for input and output, or a single combined sequence, and whether there are separate position indicators for reading and writing or a single read/write position indicator. <I>See</I> <A HREF="../guide/modes.html">Modes</A> for details.</P>


<P>A narrow-character Device for read-write access to a single character sequence with a single position indicator is called a <A HREF="../concepts/seekable_device.html">SeekableDevice</A>. A typical SeekableDevice looks like this:</P>

<PRE CLASS="broken_ie"><SPAN CLASS='preprocessor'>#include</SPAN> <SPAN CLASS="literal">&lt;iosfwd&gt;</SPAN>                           <SPAN CLASS='comment'>// streamsize, seekdir</SPAN>
<SPAN CLASS='preprocessor'>#include</SPAN> <A CLASS="HEADER" HREF="../../../../boost/iostreams/categories.hpp"><SPAN CLASS='literal'>&lt;boost/iostreams/categories.hpp&gt;</SPAN></A>   <SPAN CLASS='comment'>// seekable_device_tag</SPAN>
<SPAN CLASS='preprocessor'>#include</SPAN> <A CLASS="HEADER" HREF="../../../../boost/iostreams/positioning.hpp"><SPAN CLASS='literal'>&lt;boost/iostreams/positioning.hpp&gt;</SPAN></A>  <SPAN CLASS='comment'>// stream_offset</SPAN>

<SPAN CLASS='keyword'>namespace</SPAN> io = boost::iostreams;

<SPAN CLASS='keyword'>class</SPAN> my_device {
<SPAN CLASS='keyword'>public</SPAN>:
    <SPAN CLASS='keyword'>typedef</SPAN> <SPAN CLASS='keyword'>char</SPAN>                 char_type;
    <SPAN CLASS='keyword'>typedef</SPAN> seekable_device_tag  category;

    std::streamsize read(<SPAN CLASS='keyword'>char</SPAN>* s, std::streamsize n)
    {
        <SPAN CLASS='comment'>// Read up to n characters from the underlying data source</SPAN>
        <SPAN CLASS='comment'>// into the buffer s, returning the number of characters</SPAN>
        <SPAN CLASS='comment'>// read; return -1 to indicate EOF</SPAN>
    }

    std::streamsize write(const <SPAN CLASS='keyword'>char</SPAN>* s, std::streamsize n)
    {
        <SPAN CLASS='comment'>// Write up to n characters to the underlying </SPAN>
        <SPAN CLASS='comment'>// data sink into the buffer s, returning the </SPAN>
        <SPAN CLASS='comment'>// number of characters written</SPAN>
    }

    stream_offset seek(stream_offset off, std::ios_base::seekdir way)
    {
        <SPAN CLASS='comment'>// Seek to position off and return the new stream </SPAN>
        <SPAN CLASS='comment'>// position. The argument way indicates how off is</SPAN>
        <SPAN CLASS='comment'>// interpretted:</SPAN>
        <SPAN CLASS='comment'>//    - std::ios_base::beg indicates an offset from the </SPAN>
        <SPAN CLASS='comment'>//      sequence beginning </SPAN>
        <SPAN CLASS='comment'>//    - std::ios_base::cur indicates an offset from the </SPAN>
        <SPAN CLASS='comment'>//      current character position </SPAN>
        <SPAN CLASS='comment'>//    - std::ios_base::end indicates an offset from the </SPAN>
        <SPAN CLASS='comment'>//      sequence end </SPAN>
    }

    <SPAN CLASS='comment'>/* Other members */</SPAN>
};</PRE>

<P>Here the member type <A HREF="../guide/traits.html#char_type"><CODE>char_type</CODE></A> indicates the type of characters handled by my_source, which will almost always be <CODE>char</CODE> or <CODE>wchar_t</CODE>. The member type <A HREF="../guide/traits.html#char_type">category</A> indicates which of the fundamental i/o operations are supported by the device. The category tag <A HREF="../guide/traits.html#category_tags"><CODE>seekable_tag</CODE></A> indicates that <A HREF="../functions/read.html"><CODE>read</CODE></A>, <A HREF="../functions/write.html"><CODE>write</CODE></A> and the single-sequence version of <A HREF="../functions/seek.html"><CODE>seek</CODE></A> are supported.</P>

<P>The type <A HREF="../functions/positioning.html#synopsis"><CODE>stream_offset</CODE></A> is used by the Iostreams library to hold stream offsets.</P>

<P>You could also write the above example as follows:</P>

<PRE CLASS="broken_ie"><SPAN CLASS='preprocessor'>#include</SPAN> <A CLASS="HEADER" HREF="../../../../boost/iostreams/concepts.hpp"><SPAN CLASS='literal'>&lt;boost/iostreams/concepts.hpp&gt;</SPAN></A>  <SPAN CLASS='comment'>// seekable_device</SPAN>

<SPAN CLASS='keyword'>class</SPAN> my_source : <SPAN CLASS='keyword'>public</SPAN> seekable_device {
<SPAN CLASS='keyword'>public</SPAN>:
    std::streamsize read(<SPAN CLASS='keyword'>char</SPAN>* s, std::streamsize n);
    std::streamsize write(const <SPAN CLASS='keyword'>char</SPAN>* s, std::streamsize n);
    stream_offset seek(stream_offset off, std::ios_base::seekdir way);

    <SPAN CLASS='comment'>/* Other members */</SPAN>
};</PRE>

<P>Here <A HREF="../classes/device.html#synopsis"><CODE>seekable_device</CODE></A> is a convenience base class which provides the member types <CODE>char_type</CODE> and <CODE>category</CODE>, as well as no-op implementations of member functions <CODE>close</CODE> and <CODE>imbue</CODE>, not needed here.

<P>You're now ready to write your <CODE>container_device</CODE>. Again, let's assume your container's iterators are RandomAccessIterators.</P>

<PRE CLASS="broken_ie"><SPAN CLASS='preprocessor'>#include</SPAN> <SPAN CLASS='literal'>&lt;algorithm&gt;</SPAN>                       <SPAN CLASS='comment'>// copy, min</SPAN>
<SPAN CLASS='preprocessor'>#include</SPAN> <SPAN CLASS='literal'>&lt;iosfwd&gt;</SPAN>                          <SPAN CLASS='comment'>// streamsize</SPAN>
<SPAN CLASS='preprocessor'>#include</SPAN> <A CLASS='header' HREF="../../../../boost/iostreams/categories.hpp"><SPAN CLASS='literal'>&lt;boost/iostreams/categories.hpp&gt;</SPAN></A>  <SPAN CLASS='comment'>// source_tag</SPAN>

<SPAN CLASS='keyword'>namespace</SPAN> boost { <SPAN CLASS='keyword'>namespace</SPAN> iostreams { <SPAN CLASS='keyword'>namespace</SPAN> example {

<SPAN CLASS='keyword'>template</SPAN>&lt;<SPAN CLASS="keyword">typename</SPAN> Container&gt;
<SPAN CLASS="keyword">class</SPAN> container_device {
<SPAN CLASS="keyword">public</SPAN>:
    <SPAN CLASS="keyword">typedef</SPAN> <SPAN CLASS="keyword">typename</SPAN> Container::value_type  char_type;
    <SPAN CLASS="keyword">typedef</SPAN> seekable_device_tag             category;
    container_device(Container&amp; container)
        : container_(container), pos_(<SPAN CLASS='numeric_literal'>0</SPAN>)
        { }

    std::streamsize read(char_type* s, std::streamsize n)
    {
        <SPAN CLASS="keyword">using</SPAN> <SPAN CLASS="keyword">namespace</SPAN> std;
        streamsize amt = <SPAN CLASS="keyword">static_cast</SPAN>&lt;streamsize&gt;(container_.size() - pos_);
        streamsize result = (min)(n, amt);
        <SPAN CLASS="keyword">if</SPAN> (result != <SPAN CLASS='numeric_literal'>0</SPAN>) {
            std::copy( container_.begin() + pos_, 
                       container_.begin() + pos_ + result, 
                       s );
            pos_ += result;
            <SPAN CLASS="keyword">return</SPAN> result;
        } <SPAN CLASS="keyword">else</SPAN> {
            <SPAN CLASS="keyword">return</SPAN> <SPAN CLASS='numeric_literal'>-1</SPAN>; <SPAN CLASS='comment'>// EOF</SPAN>
        }
    }
    std::streamsize write(<SPAN CLASS="keyword">const</SPAN> char_type* s, std::streamsize n)
    {
        <SPAN CLASS="keyword">using</SPAN> <SPAN CLASS="keyword">namespace</SPAN> std;
        streamsize result = <SPAN CLASS='numeric_literal'>0</SPAN>;
        <SPAN CLASS="keyword">if</SPAN> (pos_ != container_.size()) {
            streamsize amt = 
                <SPAN CLASS="keyword">static_cast</SPAN>&lt;streamsize&gt;(container_.size() - pos_);
            result = (min)(n, amt);
            std::copy(s, s + result, container_.begin() + pos_);
            pos_ += result;
        }
        <SPAN CLASS="keyword">if</SPAN> (result < n) {
            container_.insert(container_.end(), s + result, s + n);
            pos_ = container_.size();
        }
        <SPAN CLASS="keyword">return</SPAN> n;
    }
    stream_offset seek(stream_offset off, std::ios_base::seekdir way)
    {
        <SPAN CLASS="keyword">using</SPAN> <SPAN CLASS="keyword">namespace</SPAN> std;

        <SPAN CLASS='comment'>// Determine new value of pos_</SPAN>
        stream_offset next;
        <SPAN CLASS="keyword">if</SPAN> (way == ios_base::beg) {
            next = off;
        } <SPAN CLASS="keyword">else</SPAN> <SPAN CLASS="keyword">if</SPAN> (way == ios_base::cur) {
            next = pos_ + off;
        } <SPAN CLASS="keyword">else</SPAN> <SPAN CLASS="keyword">if</SPAN> (way == ios_base::end) {
            next = container_.size() + off - <SPAN CLASS='numeric_literal'>1</SPAN>;
        } <SPAN CLASS="keyword">else</SPAN> {
            <SPAN CLASS="keyword">throw</SPAN> ios_base::failure(<SPAN CLASS='numeric_literal'>"bad seek direction"</SPAN>);
        }

        <SPAN CLASS='comment'>// Check for errors</SPAN>
        <SPAN CLASS="keyword">if</SPAN> (next < <SPAN CLASS='numeric_literal'>0</SPAN> || next >= static_cast&lt;stream_offset>(container_.size()))
            <SPAN CLASS="keyword">throw</SPAN> ios_base::failure(<SPAN CLASS='numeric_literal'>"bad seek offset"</SPAN>);

        pos_ = next;
        <SPAN CLASS="keyword">return</SPAN> pos_;
    }

    Container&amp; container() { <SPAN CLASS="keyword">return</SPAN> container_; }
<SPAN CLASS="keyword">private</SPAN>:
    <SPAN CLASS="keyword">typedef</SPAN> <SPAN CLASS="keyword">typename</SPAN> Container::size_type   size_type;
    Container&amp;  container_;
    size_type   pos_;
};

} } } <SPAN CLASS='comment'>// End namespace boost::iostreams:example</SPAN></PRE>

<P>Here, note that</P>
<UL>
<LI>The member type <CODE>char_type</CODE> is defined to be equal to the container's <CODE>value_type</CODE>;
<LI>The member type <CODE>category</CODE> tells the Iostreams library that <CODE>container_device</CODE> is a model of <A HREF="../concepts/seekable_device.html">SeekableDevice</A>;
<LI>A <CODE>container_device</CODE> can be constructed from a instance of <CODE>Container</CODE>, which is passed and stored by reference, and accessible <I>via</I> the member function <CODE>container()</CODE>;
<LI>The implementation of <CODE>read</CODE> is identical to the implementation in <A HREF="container_source.html"><CODE>container_source</CODE></A>.
</UL>

<P>The implementation of <CODE>write</CODE> is a bit different from the implementation in <A HREF="container_sink.html"><CODE>container_sink</CODE></A>. Rather than simply appending characters to the container, you first check whether the current character position is somewhere in the middle of the container. If it is, you attempt to satisfy the write request by overwriting existing characters in the container, starting at the current character position. If you reach the end of the container before the write request is satisfied, you insert the remaining characters at the end.</P>

<P>The implementation of <CODE>seek</CODE> is straightforward. First, you calculate the new character position based on <CODE>off</CODE> and <CODE>way</CODE>: if <CODE>way</CODE> is <CODE>ios_base::beg</CODE>, the new character position is simply <CODE>off</CODE>; if <CODE>way</CODE> is <CODE>ios_base::cur</CODE>, the new character position is <CODE>pos_ + off</CODE>; if <CODE>way</CODE> is <CODE>ios_base::end</CODE>, the new character position is <CODE>container_.size() + off - 1</CODE>. Next, you check whether the new character position is a valid offset, and throw an exception if it isn't. Instances of <CODE>std::basic_streambuf</CODE> are allowed to return <CODE>-1</CODE> to indicate failure, but the policy of the Boost Iostreams library is that errors should be indicated by throwing an exception (<I>see</I> <A HREF="../guide/exceptions.html">Exceptions</A>). Finally, you set the new position and return it.</P>

<P>You can use a container_device as follows</P>

<PRE CLASS="broken_ie"><SPAN CLASS='preprocessor'>#include</SPAN> <SPAN CLASS='literal'>&lt;cassert&gt;</SPAN>
<SPAN CLASS='preprocessor'>#include</SPAN> <SPAN CLASS='literal'>&lt;ios&gt;</SPAN> <SPAN CLASS='comment'>// ios_base::beg</SPAN>
<SPAN CLASS='preprocessor'>#include</SPAN> <SPAN CLASS='literal'>&lt;string&gt;</SPAN>
<SPAN CLASS='preprocessor'>#include</SPAN> <A CLASS="header" HREF="../../../../boost/iostreams/stream.hpp"><SPAN CLASS='literal'>&lt;boost/iostreams/stream.hpp&gt;</SPAN></A>
<SPAN CLASS='preprocessor'>#include</SPAN> <A CLASS="header"  HREF="../../example/container_device.hpp"><SPAN CLASS='literal'>&lt;libs/iostreams/example/container_device.hpp&gt;</SPAN></A>

<SPAN CLASS="keyword">namespace</SPAN> io = boost::iostreams;
<SPAN CLASS="keyword">namespace</SPAN> ex = boost::iostreams::example;

<SPAN CLASS="keyword">int</SPAN> main()
{
    <SPAN CLASS="keyword">using</SPAN> <SPAN CLASS="keyword">namespace</SPAN> std;
    <SPAN CLASS="keyword">typedef</SPAN> ex::container_device&lt;string&gt; string_device;

    string                     one, two;
    io::stream&lt;string_device&gt;  io(one);
    io &lt;&lt; <SPAN CLASS='literal'>"Hello World!"</SPAN>;
    io.flush();
    io.seekg(0, BOOST_IOS::beg); <SPAN CLASS='comment'>// seek to the beginning</SPAN>
    getline(io, two);
    assert(one == <SPAN CLASS='literal'>"Hello World!"</SPAN>);
    assert(two == <SPAN CLASS='literal'>"Hello World!"</SPAN>);
}</PRE>

<!-- Begin Nav -->

<DIV CLASS='nav'>
    <A HREF='container_sink.html'><IMG BORDER=0 WIDTH=19 HEIGHT=19 SRC='../../../../doc/src/images/prev.png'></A>
    <A HREF='tutorial.html'><IMG BORDER=0 WIDTH=19 HEIGHT=19 SRC='../../../../doc/src/images/up.png'></A>
    <A HREF='writing_filters.html'><IMG BORDER=0 WIDTH=19 HEIGHT=19 SRC='../../../../doc/src/images/next.png'></A>
</DIV>

<!-- End Nav -->

<!-- Begin Footer -->

<HR>


<P CLASS="copyright">&copy; Copyright 2008 <a href="http://www.coderage.com/" target="_top">CodeRage, LLC</a><br/>&copy; Copyright 2004-2007 <a href="https://www.boost.org/users/people/jonathan_turkanis.html" target="_top">Jonathan Turkanis</a></P>
<P CLASS="copyright"> 
    Use, modification, and distribution are subject to the Boost Software License, Version 2.0. (See accompanying file <A HREF="../../../../LICENSE_1_0.txt">LICENSE_1_0.txt</A> or copy at <A HREF="http://www.boost.org/LICENSE_1_0.txt">http://www.boost.org/LICENSE_1_0.txt</A>)
</P>
<!-- End Footer -->

</BODY>
